"""
缺陷管理API路由
"""
from typing import Any, Optional, List
import io
from urllib.parse import quote
from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File, Request, Body  # pyright: ignore[reportMissingImports]
from fastapi import status as http_status  # pyright: ignore[reportMissingImports]
from fastapi.responses import StreamingResponse  # pyright: ignore[reportMissingImports]
from motor.motor_asyncio import AsyncIOMotorDatabase  # pyright: ignore[reportMissingImports]
from bson import ObjectId  # pyright: ignore[reportMissingImports]
from pymongo import ASCENDING, DESCENDING  # pyright: ignore[reportMissingImports]
from datetime import datetime
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.worksheet.datavalidation import DataValidation
from openpyxl.utils import get_column_letter

from app.api.deps import get_db, get_current_user
from app.models.defect import (
    DefectSeverity,
    DefectType,
    DefectSource,
    DefectPriority
)
from app.schemas.defect import (
    DefectCreate,
    DefectUpdate,
    DefectStatusUpdate,
    DefectAssigneeUpdate,
)
from app.core.permissions import Permissions
from app.core.response import success_response, error_response, paginated_response, ErrorTypes, ApiResponse
from app.utils.task_utils import format_defect_response
from app.utils.state_utils import (
    validate_state_code,
    get_state_code_by_name_keywords
)
from app.services.state_service import StateService
from app.services.permission_service import PermissionService
from app.services.notification_service import NotificationService
from app.services.defect_service import DefectService
from app.models.notification import NotificationCreate, NotificationType, RelatedObjectType
import asyncio
import logging

router = APIRouter()
logger = logging.getLogger(__name__)

def get_defect_service(db: AsyncIOMotorDatabase = Depends(get_db)) -> DefectService:
    """获取缺陷服务实例"""
    return DefectService(db)

@router.get("/", response_model=None, summary="获取缺陷列表（分页）")
async def get_defects(
    skip: int = Query(0, ge=0, description="跳过的记录数"),
    limit: int = Query(20, ge=1, le=100, description="返回的记录数"),
    search: Optional[str] = Query(None, description="搜索关键词"),
    project_id: Optional[str] = Query(None, description="项目ID筛选"),
    requirement_id: Optional[str] = Query(None, description="需求ID筛选"),
    assignee_id: Optional[str] = Query(None, description="负责人筛选"),
    reporter_id: Optional[str] = Query(None, description="报告人筛选"),
    status: Optional[str] = Query(None, description="状态筛选"),
    priority: Optional[DefectPriority] = Query(None, description="优先级筛选"),
    severity: Optional[DefectSeverity] = Query(None, description="严重程度筛选"),
    defect_type: Optional[DefectType] = Query(None, description="缺陷类型筛选"),
    source: Optional[DefectSource] = Query(None, description="缺陷来源筛选"),
    found_date_start: Optional[str] = Query(None, description="发现日期开始"),
    found_date_end: Optional[str] = Query(None, description="截止日期结束"),
    tags: Optional[str] = Query(None, description="标签筛选（逗号分隔）"),
    version: Optional[str] = Query(None, description="版本筛选"),
    version_id: Optional[str] = Query(None, description="版本ID筛选（通过关联表）"),
    sort_by: str = Query("created_at", description="排序字段"),
    sort_order: str = Query("desc", regex="^(asc|desc)$", description="排序方向"),
    current_user: dict = Depends(get_current_user),
    defect_service: DefectService = Depends(get_defect_service)
) -> Any:
    """
    获取缺陷列表
    """
    try:
        # 参数验证和处理空字符串
        if search == "":
            search = None
        if status == "":
            status = None
        if project_id == "":
            project_id = None
        if requirement_id == "":
            requirement_id = None
        if assignee_id == "":
            assignee_id = None
        if reporter_id == "":
            reporter_id = None
        if version_id == "":
            version_id = None
        if tags == "":
            tags = None
        if version == "":
            version = None
        if found_date_start == "":
            found_date_start = None
        if found_date_end == "":
            found_date_end = None
        if sort_order == "":
            sort_order = "desc"
        
        # 调用服务层方法，所有业务逻辑都在service层处理
        defects = await defect_service.get_defects(
            user=current_user,
            skip=skip,
            limit=limit,
            search=search,
            project_id=project_id,
            requirement_id=requirement_id,
            assignee_id=assignee_id,
            reporter_id=reporter_id,
            status=status,
            priority=priority,
            severity=severity,
            defect_type=defect_type,
            source=source,
            found_date_start=found_date_start,
            found_date_end=found_date_end,
            tags=tags,
            version_id=version_id,
            sort_by=sort_by,
            sort_order=sort_order,
        )
        
        # 获取总数
        total = await defect_service.get_defects_count(
            user=current_user,
            search=search,
            project_id=project_id,
            requirement_id=requirement_id,
            assignee_id=assignee_id,
            reporter_id=reporter_id,
            status=status,
            priority=priority,
            severity=severity,
            defect_type=defect_type,
            source=source,
            found_date_start=found_date_start,
            found_date_end=found_date_end,
            tags=tags,
            version_id=version_id,
        )
        
        # 计算分页信息
        page = (skip // limit) + 1 if limit > 0 else 1
        
        return paginated_response(
            items=defects,
            total=total,
            page=page,
            size=limit,
            message="获取缺陷列表成功"
        )
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取缺陷列表失败: {str(e)}", exc_info=True)
        return error_response(
            error_type=ErrorTypes.INTERNAL_ERROR,
            message=f"获取缺陷列表失败: {str(e)}"
        )

@router.get("/count", response_model=ApiResponse, summary="获取缺陷总数")
async def get_defects_count(
    search: Optional[str] = Query(None, description="搜索关键词"),
    project_id: Optional[str] = Query(None, description="项目ID筛选"),
    assignee_id: Optional[str] = Query(None, description="负责人筛选"),
    reporter_id: Optional[str] = Query(None, description="报告人筛选"),
    status: Optional[str] = Query(None, description="状态筛选"),
    priority: Optional[DefectPriority] = Query(None, description="优先级筛选"),
    severity: Optional[DefectSeverity] = Query(None, description="严重程度筛选"),
    defect_type: Optional[DefectType] = Query(None, description="缺陷类型筛选"),
    source: Optional[DefectSource] = Query(None, description="缺陷来源筛选"),
    found_date_start: Optional[str] = Query(None, description="发现日期开始"),
    found_date_end: Optional[str] = Query(None, description="发现日期结束"),
    tags: Optional[str] = Query(None, description="标签筛选（逗号分隔）"),
    version_id: Optional[str] = Query(None, description="版本ID筛选"),
    current_user: dict = Depends(get_current_user),
    defect_service: DefectService = Depends(get_defect_service)
) -> Any:
    """
    获取缺陷总数
    """
    try:
        # 调用服务层方法，所有业务逻辑都在service层处理
        total = await defect_service.get_defects_count(
            user=current_user,
            search=search,
            project_id=project_id,
            assignee_id=assignee_id,
            reporter_id=reporter_id,
            status=status,
            priority=priority,
            severity=severity,
            defect_type=defect_type,
            source=source,
            found_date_start=found_date_start,
            found_date_end=found_date_end,
            tags=tags,
            version_id=version_id,
        )
        
        return success_response(data={"total": total}, message="获取缺陷总数成功")
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取缺陷总数失败: {str(e)}", exc_info=True)
        return error_response(
            error_type=ErrorTypes.INTERNAL_ERROR,
            message=f"获取缺陷总数失败: {str(e)}"
        )

@router.get("/options", response_model=ApiResponse, summary="获取缺陷模块选项（状态、优先级、标签）")
async def get_defect_options(
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    获取缺陷模块的所有选项数据，包括状态、优先级、标签
    用于前端下拉选择等场景，避免硬编码
    """
    try:
        from app.utils.state_utils import get_module_states
        from app.services.state_service import StateService
        
        # 获取状态选项
        states = await StateService.get_module_states(db, "defect", force_refresh=False)
        status_options = [
            {
                "code": state.code,
                "name": state.name,
                "color": state.color or "#909399",
                "description": state.description
            }
            for state in states
            if state.is_enabled
        ]
        
        # 获取优先级选项
        priority_query = {"module_type": "defect", "is_enabled": True}
        priority_cursor = db.priorities.find(priority_query).sort("sort_order", 1)
        priorities = await priority_cursor.to_list(length=None)
        priority_options = [
            {
                "code": p["code"],
                "name": p["name"],
                "color": p.get("color", "#909399"),
                "description": p.get("description"),
                "sort_order": p.get("sort_order", 0)
            }
            for p in priorities
        ]
        
        # 获取标签选项
        tag_query = {
            "$or": [
                {"module_type": "defect"},
                {"module_type": None},
                {"module_type": {"$exists": False}}
            ],
            "is_system": {"$ne": True}
        }
        tag_cursor = db.tags.find(tag_query).sort("name", 1)
        tags = await tag_cursor.to_list(length=None)
        tag_options = [
            {
                "id": str(tag["_id"]),
                "name": tag["name"],
                "color": tag.get("color", "#1890ff"),
                "description": tag.get("description")
            }
            for tag in tags
        ]
        
        # 严重程度选项（暂时保持硬编码，因为这是缺陷模块特有的概念）
        # 如果将来需要动态管理，可以创建单独的模块或使用优先级模块
        severity_options = [
            {"code": "trivial", "name": "轻微", "color": "#C0C4CC"},
            {"code": "minor", "name": "次要", "color": "#909399"},
            {"code": "major", "name": "主要", "color": "#E6A23C"},
            {"code": "important", "name": "重要", "color": "#F56C6C"},
            {"code": "critical", "name": "严重", "color": "#F56C6C"},
            {"code": "urgent", "name": "紧急", "color": "#F56C6C"},
            {"code": "blocker", "name": "阻塞", "color": "#F56C6C"}
        ]
        
        options_data = {
            "status": status_options,
            "priority": priority_options,
            "severity": severity_options,
            "tags": tag_options
        }
        
        return success_response(
            data=options_data,
            message="获取缺陷模块选项成功"
        )
        
    except Exception as e:
        return error_response(
            error_type=ErrorTypes.INTERNAL_ERROR,
            message=f"获取缺陷模块选项失败: {str(e)}"
        )

@router.get("/stats/overview", response_model=ApiResponse, summary="获取缺陷统计概览")
async def get_defect_stats(
    project_id: Optional[str] = Query(None, description="项目ID筛选"),
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    获取缺陷统计概览
    """
    # 构建查询条件
    query = {}
    
    # 权限控制
    user_permissions = current_user.get("permissions", [])
    user_id = ObjectId(current_user.get("user_id"))
    
    if Permissions.DEFECT_READ_ALL not in user_permissions and Permissions.ADMIN not in user_permissions:
        user_projects = await db.projects.find({
            "$or": [
                {"is_public": True},
                {"created_by": user_id},
                {"members.user_id": user_id}
            ]
        }, {"_id": 1}).to_list(length=None)
        
        project_ids = [project["_id"] for project in user_projects]
        
        query["$or"] = [
            {"assignee_id": user_id},
            {"reporter_id": user_id},
            {"project_id": {"$in": project_ids}}
        ]
    
    if project_id:
        try:
            query["project_id"] = ObjectId(project_id)
        except Exception:
            raise HTTPException(
                status_code=http_status.HTTP_400_BAD_REQUEST,
                detail="Invalid project_id format"
            )
    
    # 统计缺陷总数
    total_defects = await db.defects.count_documents(query)
    
    # 按状态分组统计
    pipeline = [
        {"$match": query},
        {"$group": {"_id": "$status", "count": {"$sum": 1}}}
    ]
    status_stats = await db.defects.aggregate(pipeline).to_list(length=None)
    defects_by_status = {stat["_id"]: stat["count"] for stat in status_stats}
    
    # 按优先级分组统计
    pipeline = [
        {"$match": query},
        {"$group": {"_id": "$priority", "count": {"$sum": 1}}}
    ]
    priority_stats = await db.defects.aggregate(pipeline).to_list(length=None)
    defects_by_priority = {stat["_id"]: stat["count"] for stat in priority_stats}
    
    # 按严重程度分组统计
    pipeline = [
        {"$match": query},
        {"$group": {"_id": "$severity", "count": {"$sum": 1}}}
    ]
    severity_stats = await db.defects.aggregate(pipeline).to_list(length=None)
    defects_by_severity = {stat["_id"]: stat["count"] for stat in severity_stats}
    
    # 按类型分组统计
    pipeline = [
        {"$match": query},
        {"$group": {"_id": "$type", "count": {"$sum": 1}}}
    ]
    type_stats = await db.defects.aggregate(pipeline).to_list(length=None)
    defects_by_type = {stat["_id"]: stat["count"] for stat in type_stats}
    
    # 按来源分组统计
    pipeline = [
        {"$match": query},
        {"$group": {"_id": "$source", "count": {"$sum": 1}}}
    ]
    source_stats = await db.defects.aggregate(pipeline).to_list(length=None)
    defects_by_source = {stat["_id"]: stat["count"] for stat in source_stats}
    
    stats_data = {
        "total_defects": total_defects,
        "defects_by_status": defects_by_status,
        "defects_by_priority": defects_by_priority,
        "defects_by_severity": defects_by_severity,
        "defects_by_type": defects_by_type,
        "defects_by_source": defects_by_source
    }
    
    return success_response(data=stats_data, message="获取缺陷统计成功")

@router.get("/export-template", summary="下载缺陷导入模板")
async def download_defect_template(
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    下载缺陷导入模板文件
    """
    # 检查权限
    user_permissions = current_user.get("permissions", [])
    if Permissions.DEFECT_CREATE not in user_permissions and Permissions.ADMIN not in user_permissions:
        raise HTTPException(
            status_code=http_status.HTTP_403_FORBIDDEN,
            detail="Not enough permissions to download import template"
        )
    
    try:
        # 创建Excel工作簿
        wb = Workbook()
        ws = wb.active
        ws.title = "缺陷导入模板"
        
        # 设置表头
        headers = [
            "标题", "描述", "项目", "缺陷类型", "状态", "优先级", "严重程度",
            "缺陷来源", "报告人", "负责人", "验证人", "发现时间", "标签", "复现步骤", "期望结果", "实际结果"
        ]
        ws.append(headers)
        
        # 设置表头样式
        header_font = Font(bold=True, color="FFFFFF", size=11)
        required_fill = PatternFill(start_color="DC2626", end_color="DC2626", fill_type="solid")
        optional_fill = PatternFill(start_color="2563EB", end_color="2563EB", fill_type="solid")
        header_alignment = Alignment(horizontal="center", vertical="center")
        
        # 定义哪些字段是必填的
        required_fields = [True, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False]
        
        for col_num, (header, required) in enumerate(zip(headers, required_fields), 1):
            cell = ws.cell(row=1, column=col_num)
            cell.font = header_font
            cell.fill = required_fill if required else optional_fill
            cell.alignment = header_alignment
            ws.column_dimensions[cell.column_letter].width = max(15, len(header) + 6)
        
        # 获取状态选项（中文名称）
        states = await StateService.get_module_states(db, "defect", force_refresh=False)
        status_display_values = sorted({state.name for state in states if state.is_enabled and state.name})
        
        # 获取优先级选项（中文名称）
        priority_query = {"module_type": "defect", "is_enabled": True}
        priority_cursor = db.priorities.find(priority_query).sort("sort_order", 1)
        priorities = await priority_cursor.to_list(length=None)
        priority_display_values = [p.get("name", "") for p in priorities if p.get("name")]
        
        # 缺陷类型选项
        type_display_values = ["功能性缺陷", "界面缺陷", "性能缺陷", "兼容性缺陷", "安全性缺陷", "其他"]
        
        # 严重程度选项
        severity_display_values = ["轻微", "次要", "主要", "重要", "严重", "紧急", "阻塞"]
        
        # 缺陷来源选项
        source_display_values = ["测试", "用户反馈", "代码审查", "自动化测试", "生产环境", "其他"]
        
        # 添加数据验证
        def add_validation(column_index: int, values: list) -> None:
            if not values:
                return
            formula = '"' + ",".join(values) + '"'
            dv = DataValidation(type="list", formula1=formula, allow_blank=True, showDropDown=True)
            col_letter = get_column_letter(column_index)
            dv.ranges.add(f"{col_letter}2:{col_letter}1048576")
            ws.add_data_validation(dv)
        
        # 为相应列添加下拉验证
        column_mapping = {
            4: type_display_values,  # 缺陷类型
            5: status_display_values,  # 状态
            6: priority_display_values,  # 优先级
            7: severity_display_values,  # 严重程度
            8: source_display_values,  # 缺陷来源
        }
        
        for col_idx, values in column_mapping.items():
            add_validation(col_idx, values)
        
        # 添加示例数据行（第二行）
        example_data = [
            "示例缺陷标题", "这是缺陷描述", "示例项目", "功能性缺陷", status_display_values[0] if status_display_values else "",
            priority_display_values[0] if priority_display_values else "", "主要", "测试", "张三", "李四", "",
            "2025-01-01 10:00:00", "缺陷,重要", "步骤1：打开应用\n步骤2：执行操作\n步骤3：观察结果",
            "期望的正常行为", "实际发生的错误行为"
        ]
        ws.append(example_data)
        
        # 设置示例数据样式
        example_font = Font(color="999999", size=10)
        for col_num in range(1, len(headers) + 1):
            cell = ws.cell(row=2, column=col_num)
            cell.font = example_font
        
        # 添加说明行（第三行）
        instructions = [
            "必填", "可选", "必填（项目名称）", "可选", "可选", "可选", "可选", "可选", 
            "可选（用户姓名）", "可选（用户姓名）", "可选（用户姓名）", "可选（YYYY-MM-DD HH:mm:ss）", 
            "可选（逗号分隔）", "可选", "可选", "可选"
        ]
        ws.append(instructions)
        
        # 设置说明行样式
        instruction_font = Font(italic=True, color="666666", size=9)
        instruction_alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
        for col_num, instruction in enumerate(instructions, 1):
            cell = ws.cell(row=3, column=col_num)
            cell.font = instruction_font
            cell.alignment = instruction_alignment
        
        # 冻结第一行
        ws.freeze_panes = "A2"
        
        # 保存到内存
        output = io.BytesIO()
        wb.save(output)
        output.seek(0)
        
        # 生成文件名
        filename_cn = f"缺陷导入模板_{datetime.now().strftime('%Y%m%d')}.xlsx"
        filename_en = f"defect_import_template_{datetime.now().strftime('%Y%m%d')}.xlsx"
        
        # 编码文件名以支持中文（使用 RFC 5987 格式）
        encoded_filename = quote(filename_cn.encode('utf-8'), safe='')
        content_disposition = f"attachment; filename=\"{filename_en}\"; filename*=UTF-8''{encoded_filename}"
        
        # 返回文件流
        return StreamingResponse(
            io.BytesIO(output.read()),
            media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            headers={"Content-Disposition": content_disposition}
        )
    except Exception as e:
        import logging
        logger = logging.getLogger(__name__)
        logger.error(f"生成缺陷导入模板失败: {str(e)}", exc_info=True)
        return error_response(
            error_type=ErrorTypes.IMPORT_TEMPLATE_ERROR,
            message=f"生成缺陷导入模板失败: {str(e)}",
            code=500
        )

@router.get("/export", summary="导出缺陷为Excel")
async def export_defects(
    search: Optional[str] = Query(None, description="搜索关键词"),
    project_id: Optional[str] = Query(None, description="项目ID筛选"),
    assignee_id: Optional[str] = Query(None, description="负责人筛选"),
    reporter_id: Optional[str] = Query(None, description="报告人筛选"),
    status: Optional[str] = Query(None, description="状态筛选"),
    priority: Optional[DefectPriority] = Query(None, description="优先级筛选"),
    severity: Optional[DefectSeverity] = Query(None, description="严重程度筛选"),
    defect_type: Optional[DefectType] = Query(None, description="缺陷类型筛选"),
    source: Optional[DefectSource] = Query(None, description="缺陷来源筛选"),
    found_date_start: Optional[str] = Query(None, description="发现日期开始"),
    found_date_end: Optional[str] = Query(None, description="发现日期结束"),
    tags: Optional[str] = Query(None, description="标签筛选（逗号分隔）"),
    defect_ids: Optional[str] = Query(None, description="缺陷ID列表（逗号分隔，如果提供则只导出这些缺陷）"),
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    导出缺陷为Excel文件
    """
    # 检查权限
    user_permissions = current_user.get("permissions", [])
    if Permissions.DEFECT_READ_ALL not in user_permissions and Permissions.ADMIN not in user_permissions:
        raise HTTPException(
            status_code=http_status.HTTP_403_FORBIDDEN,
            detail="Not enough permissions to export defects"
        )
    
    try:
        # 构建查询条件（与get_defects相同的逻辑）
        query = {}
        user_id = ObjectId(current_user.get("user_id"))
        
        if Permissions.DEFECT_READ_ALL not in user_permissions and Permissions.ADMIN not in user_permissions:
            user_projects = await db.projects.find({
                "$or": [
                    {"is_public": True},
                    {"created_by": user_id},
                    {"members.user_id": user_id}
                ]
            }, {"_id": 1}).to_list(length=None)
            project_ids = [project["_id"] for project in user_projects]
            query["$or"] = [
                {"assignee_id": user_id},
                {"reporter_id": user_id},
                {"project_id": {"$in": project_ids}}
            ]
        
        # 如果指定了缺陷ID列表，只导出这些缺陷
        if defect_ids:
            try:
                defect_id_list = [did.strip() for did in defect_ids.split(",") if did.strip()]
                defect_id_objs = []
                for did in defect_id_list:
                    try:
                        defect_id_objs.append(ObjectId(did))
                    except Exception:
                        pass
                if defect_id_objs:
                    query["_id"] = {"$in": defect_id_objs}
            except Exception:
                pass
        
        # 应用其他筛选条件
        if search:
            query["$and"] = query.get("$and", [])
            query["$and"].append({
                "$or": [
                    {"title": {"$regex": search, "$options": "i"}},
                    {"description": {"$regex": search, "$options": "i"}},
                    {"steps_to_reproduce": {"$regex": search, "$options": "i"}},
                    {"tags": {"$regex": search, "$options": "i"}}  # 修复：直接使用 $regex，MongoDB 会在数组的每个元素上应用正则表达式
                ]
            })
        
        if project_id:
            try:
                query["project_id"] = ObjectId(project_id)
            except Exception:
                pass
        
        if assignee_id:
            try:
                query["assignee_id"] = ObjectId(assignee_id)
            except Exception:
                pass
        
        if reporter_id:
            try:
                query["reporter_id"] = ObjectId(reporter_id)
            except Exception:
                pass
        
        if status:
            query["status"] = status
        
        if priority:
            query["priority"] = priority
        
        if severity:
            query["severity"] = severity
        
        if defect_type:
            query["type"] = defect_type
        
        if source:
            query["source"] = source
        
        if found_date_start or found_date_end:
            date_query = {}
            if found_date_start:
                try:
                    date_query["$gte"] = datetime.fromisoformat(found_date_start.replace('Z', '+00:00'))
                except ValueError:
                    pass
            if found_date_end:
                try:
                    date_query["$lte"] = datetime.fromisoformat(found_date_end.replace('Z', '+00:00'))
                except ValueError:
                    pass
            if date_query:
                query["found_date"] = date_query
        
        if tags:
            tag_list = [tag.strip() for tag in tags.split(",")]
            query["tags"] = {"$in": tag_list}
        
        # 查询缺陷
        defects = await db.defects.find(query).sort("created_at", DESCENDING).to_list(length=None)
        
        # 批量查询关联数据
        project_ids = set()
        user_ids = set()
        for defect in defects:
            if defect.get("project_id"):
                project_ids.add(defect["project_id"])
            if defect.get("assignee_id"):
                user_ids.add(defect["assignee_id"])
            if defect.get("reporter_id"):
                user_ids.add(defect["reporter_id"])
            if defect.get("verifier_id"):
                user_ids.add(defect["verifier_id"])
        
        projects_dict = {}
        if project_ids:
            projects = await db.projects.find(
                {"_id": {"$in": list(project_ids)}},
                {"_id": 1, "name": 1}
            ).to_list(length=None)
            projects_dict = {str(p["_id"]): p.get("name", "") for p in projects}
        
        users_dict = {}
        if user_ids:
            users = await db.users.find(
                {"_id": {"$in": list(user_ids)}},
                {"_id": 1, "name": 1}
            ).to_list(length=None)
            users_dict = {str(u["_id"]): u.get("name", "") for u in users}
        
        # 创建Excel工作簿
        wb = Workbook()
        ws = wb.active
        ws.title = "缺陷列表"
        
        # 设置表头
        headers = [
            "缺陷ID", "标题", "描述", "项目", "项目ID", "缺陷类型", "状态", "优先级", "严重程度",
            "缺陷来源", "报告人", "负责人", "验证人", "发现时间", "标签", "复现步骤", "期望结果", "实际结果",
            "创建时间", "更新时间"
        ]
        ws.append(headers)
        
        # 设置表头样式
        header_font = Font(bold=True, color="FFFFFF", size=11)
        header_fill = PatternFill(start_color="1F2937", end_color="1F2937", fill_type="solid")
        header_alignment = Alignment(horizontal="center", vertical="center")
        
        for col_num, header in enumerate(headers, 1):
            cell = ws.cell(row=1, column=col_num)
            cell.font = header_font
            cell.fill = header_fill
            cell.alignment = header_alignment
            ws.column_dimensions[cell.column_letter].width = max(15, len(header) + 6)
        
        # 填充数据
        for defect in defects:
            project_id_str = str(defect.get("project_id", "")) if defect.get("project_id") else ""
            project_name = projects_dict.get(project_id_str, "")
            
            assignee_id_str = str(defect.get("assignee_id", "")) if defect.get("assignee_id") else ""
            reporter_id_str = str(defect.get("reporter_id", "")) if defect.get("reporter_id") else ""
            verifier_id_str = str(defect.get("verifier_id", "")) if defect.get("verifier_id") else ""
            
            assignee_name = users_dict.get(assignee_id_str, "")
            reporter_name = users_dict.get(reporter_id_str, "")
            verifier_name = users_dict.get(verifier_id_str, "")
            
            found_date = defect.get("found_date")
            if isinstance(found_date, datetime):
                found_date_str = found_date.strftime("%Y-%m-%d %H:%M:%S")
            else:
                found_date_str = str(found_date) if found_date else ""
            
            created_at = defect.get("created_at")
            if isinstance(created_at, datetime):
                created_at_str = created_at.strftime("%Y-%m-%d %H:%M:%S")
            else:
                created_at_str = str(created_at) if created_at else ""
            
            updated_at = defect.get("updated_at")
            if isinstance(updated_at, datetime):
                updated_at_str = updated_at.strftime("%Y-%m-%d %H:%M:%S")
            else:
                updated_at_str = str(updated_at) if updated_at else ""
            
            tag_text = ", ".join(defect.get("tags", []))
            
            row_data = [
                str(defect.get("_id", "")),
                defect.get("title", ""),
                defect.get("description", ""),
                project_name,
                project_id_str,
                defect.get("type", ""),
                defect.get("status", ""),
                defect.get("priority", ""),
                defect.get("severity", ""),
                defect.get("source", ""),
                reporter_name,
                assignee_name,
                verifier_name,
                found_date_str,
                tag_text,
                defect.get("steps_to_reproduce", ""),
                defect.get("expected_result", ""),
                defect.get("actual_result", ""),
                created_at_str,
                updated_at_str
            ]
            ws.append(row_data)
        
        # 保存到内存
        output = io.BytesIO()
        wb.save(output)
        output.seek(0)
        
        # 生成文件名
        filename_cn = f"缺陷导出_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
        filename_en = f"defects_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
        
        # 编码文件名以支持中文
        encoded_filename = quote(filename_cn.encode('utf-8'), safe='')
        content_disposition = f"attachment; filename=\"{filename_en}\"; filename*=UTF-8''{encoded_filename}"
        
        # 返回文件流
        return StreamingResponse(
            io.BytesIO(output.read()),
            media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            headers={"Content-Disposition": content_disposition}
        )
    except Exception as e:
        import logging
        logger = logging.getLogger(__name__)
        logger.error(f"导出缺陷失败: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=http_status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"导出缺陷失败: {str(e)}"
        )

@router.post("/import", response_model=ApiResponse, summary="导入缺陷（Excel格式）")
async def import_defects(
    file: UploadFile = File(...),
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    从Excel文件导入缺陷数据
    """
    # 检查权限
    user_permissions = current_user.get("permissions", [])
    if Permissions.DEFECT_CREATE not in user_permissions and Permissions.ADMIN not in user_permissions:
        return error_response(
            error_type=ErrorTypes.PERMISSION_DENIED_ERROR,
            message="Not enough permissions to import defects",
            code=403
        )
    
    # 验证文件类型
    if not file.filename or not file.filename.endswith(('.xlsx', '.xls')):
        return error_response(
            error_type=ErrorTypes.INVALID_PARAMETER,
            message="只支持 Excel 文件 (.xlsx, .xls)",
            code=400
        )
    
    success_count = 0
    error_count = 0
    errors: list = []
    
    try:
        # 读取文件内容
        content = await file.read()
        wb = load_workbook(io.BytesIO(content), data_only=True)
        ws = wb.active
        
        # 读取表头（跳过示例行和说明行，从第一行开始）
        headers = []
        for cell in ws[1]:
            headers.append(cell.value if cell.value else "")
        
        # 创建列映射
        header_map = {header: idx for idx, header in enumerate(headers) if header}
        
        # 验证必需的列
        required_columns = ["标题", "项目"]
        missing_columns = [col for col in required_columns if col not in header_map]
        if missing_columns:
            return error_response(
                error_type=ErrorTypes.INVALID_PARAMETER,
                message=f"缺少必需的列: {', '.join(missing_columns)}",
                code=400
            )
        
        # 获取状态映射
        states = await StateService.get_module_states(db, "defect", force_refresh=False)
        status_name_map = {state.name.lower(): state.code for state in states if state.is_enabled and state.name}
        
        # 获取优先级映射
        priority_query = {"module_type": "defect", "is_enabled": True}
        priority_cursor = db.priorities.find(priority_query).sort("sort_order", 1)
        priorities = await priority_cursor.to_list(length=None)
        priority_name_map = {p.get("name", "").lower(): p.get("code", "") for p in priorities if p.get("name")}
        
        # 类型映射
        type_map = {
            "功能性缺陷": "functional",
            "界面缺陷": "ui",
            "性能缺陷": "performance",
            "兼容性缺陷": "compatibility",
            "安全性缺陷": "security",
            "其他": "other"
        }
        
        # 严重程度映射
        severity_map = {
            "轻微": "trivial",
            "次要": "minor",
            "主要": "major",
            "重要": "important",
            "严重": "critical",
            "紧急": "urgent",
            "阻塞": "blocker"
        }
        
        # 来源映射
        source_map = {
            "测试": "testing",
            "用户反馈": "user_feedback",
            "代码审查": "code_review",
            "自动化测试": "automated_test",
            "生产环境": "production",
            "其他": "other"
        }
        
        # 从第2行开始读取数据（跳过表头、示例行、说明行）
        for row_idx, row in enumerate(ws.iter_rows(min_row=2, values_only=False), start=2):
            try:
                # 获取单元格值
                def get_cell_value(col_name: str, default: str = ""):
                    if col_name in header_map:
                        cell = row[header_map[col_name]]
                        value = cell.value if cell.value is not None else ""
                        return str(value).strip() if value else default
                    return default
                
                # 验证必需字段
                title = get_cell_value("标题")
                project_name = get_cell_value("项目")
                
                if not title:
                    errors.append(f"第{row_idx}行: 标题不能为空")
                    error_count += 1
                    continue
                
                if not project_name:
                    errors.append(f"第{row_idx}行: 项目不能为空")
                    error_count += 1
                    continue
                
                # 查找项目
                project = await db.projects.find_one({"name": project_name})
                if not project:
                    errors.append(f"第{row_idx}行: 项目 '{project_name}' 不存在")
                    error_count += 1
                    continue
                
                project_id = str(project["_id"])
                
                # 解析其他字段
                description = get_cell_value("描述")
                defect_type_str = get_cell_value("缺陷类型")
                defect_type = type_map.get(defect_type_str, "functional")
                
                status_str = get_cell_value("状态")
                # 从状态管理系统获取默认状态
                from app.utils.state_utils import get_default_state_code
                default_status = await get_default_state_code(db, "defect")
                status = status_name_map.get(status_str.lower(), default_status or "") if status_str else (default_status or "")
                
                priority_str = get_cell_value("优先级")
                priority = priority_name_map.get(priority_str.lower(), "medium") if priority_str else "medium"
                
                severity_str = get_cell_value("严重程度")
                severity = severity_map.get(severity_str, "minor") if severity_str else "minor"
                
                source_str = get_cell_value("缺陷来源")
                source = source_map.get(source_str, "testing") if source_str else "testing"
                
                # 查找用户
                reporter_name = get_cell_value("报告人")
                reporter_id = None
                if reporter_name:
                    reporter = await db.users.find_one({"name": reporter_name})
                    if reporter:
                        reporter_id = str(reporter["_id"])
                    else:
                        # 如果报告人不存在，使用当前用户作为报告人
                        reporter_id = current_user.get("user_id")
                else:
                    reporter_id = current_user.get("user_id")
                
                assignee_name = get_cell_value("负责人")
                assignee_id = None
                if assignee_name:
                    assignee = await db.users.find_one({"name": assignee_name})
                    if assignee:
                        assignee_id = str(assignee["_id"])
                
                verifier_name = get_cell_value("验证人")
                verifier_id = None
                if verifier_name:
                    verifier = await db.users.find_one({"name": verifier_name})
                    if verifier:
                        verifier_id = str(verifier["_id"])
                
                # 解析日期
                found_date_str = get_cell_value("发现时间")
                found_date = None
                if found_date_str:
                    try:
                        found_date = datetime.strptime(found_date_str, "%Y-%m-%d %H:%M:%S")
                    except ValueError:
                        try:
                            found_date = datetime.strptime(found_date_str, "%Y-%m-%d")
                        except ValueError:
                            pass
                
                # 解析标签
                tags_str = get_cell_value("标签")
                tags = [tag.strip() for tag in tags_str.split(",") if tag.strip()] if tags_str else []
                
                steps_to_reproduce = get_cell_value("复现步骤")
                expected_result = get_cell_value("期望结果")
                actual_result = get_cell_value("实际结果")
                
                # 创建缺陷文档
                defect_doc = {
                    "title": title,
                    "description": description if description else None,
                    "type": defect_type,
                    "status": status,
                    "priority": priority,
                    "severity": severity,
                    "source": source,
                    "project_id": ObjectId(project_id),
                    "assignee_id": ObjectId(assignee_id) if assignee_id else None,
                    "reporter_id": ObjectId(reporter_id) if reporter_id else None,
                    "verifier_id": ObjectId(verifier_id) if verifier_id else None,
                    "steps_to_reproduce": steps_to_reproduce if steps_to_reproduce else None,
                    "expected_result": expected_result if expected_result else None,
                    "actual_result": actual_result if actual_result else None,
                    "found_date": found_date,
                    "tags": tags,
                    "created_by": ObjectId(current_user.get("user_id")),
                    "created_at": datetime.utcnow(),
                    "updated_at": datetime.utcnow(),
                    "comments": [],
                    "attachments": []
                }
                
                # 插入数据库
                await db.defects.insert_one(defect_doc)
                success_count += 1
                
            except Exception as e:
                error_count += 1
                errors.append(f"第{row_idx}行: {str(e)}")
        
        return success_response(
            data={
                "success_count": success_count,
                "error_count": error_count,
                "errors": errors
            },
            message=f"导入完成: 成功 {success_count} 条, 失败 {error_count} 条"
        )
        
    except Exception as e:
        import logging
        logger = logging.getLogger(__name__)
        logger.error(f"导入缺陷失败: {str(e)}", exc_info=True)
        return error_response(
            error_type=ErrorTypes.INTERNAL_ERROR,
            message=f"导入缺陷失败: {str(e)}",
            code=500
        )

@router.get("/{defect_id}", response_model=ApiResponse, summary="获取缺陷详情")
async def get_defect(
    defect_id: str,
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    获取缺陷详情
    """
    # 验证ObjectId格式
    try:
        object_id = ObjectId(defect_id)
    except Exception:
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail="Invalid defect ID format"
        )
    
    # 查询缺陷
    defect = await db.defects.find_one({"_id": object_id})
    if not defect:
        raise HTTPException(
            status_code=http_status.HTTP_404_NOT_FOUND,
            detail="Defect not found"
        )
    
    # 使用统一的数据权限检查方法
    has_access = await PermissionService.check_data_access(
        user=current_user,
        db=db,
        data_item=defect,
        project_id_field="project_id",
        assignee_id_field="assignee_id",
        reporter_id_field="reporter_id",
        creator_id_field="created_by",
        verifier_id_field="verifier_id"  # 缺陷有验证人字段
    )
    
    if not has_access:
        raise HTTPException(
            status_code=http_status.HTTP_403_FORBIDDEN,
            detail="Not enough permissions to access this defect"
        )
    
    # 查询缺陷关联的所有版本信息
    # 注意：需要确保 defect_id 和 version_id 的类型匹配（都是 ObjectId）
    defect_relations = await db.version_defect_relations.find({"defect_id": object_id}).to_list(length=None)
    versions_list = []
    
    # 调试：检查关联表查询结果
    if defect_relations:
        version_ids = []
        for relation in defect_relations:
            version_id = relation.get("version_id")
            if version_id:
                # 确保 version_id 是 ObjectId 类型
                if isinstance(version_id, ObjectId):
                    version_ids.append(version_id)
                elif isinstance(version_id, str) and ObjectId.is_valid(version_id):
                    version_ids.append(ObjectId(version_id))
        
        # 批量查询版本信息
        if version_ids:
            versions = await db.versions.find(
                {"_id": {"$in": version_ids}},
                {"_id": 1, "name": 1, "version_number": 1}  # 只查询这些字段
            ).to_list(length=None)
            
            # 确保查询到的版本数量与关联记录一致
            if versions:
                if len(versions) < len(version_ids):
                    # 记录警告：部分版本数据可能已被删除
                    missing_count = len(version_ids) - len(versions)
                    found_version_ids = {str(v["_id"]) for v in versions}
                    missing_version_ids = [str(vid) for vid in version_ids if str(vid) not in found_version_ids]
                    # 可以在这里添加日志记录
                    # logger.warning(f"缺陷 {defect_id} 关联的 {missing_count} 个版本数据不存在: {missing_version_ids}")
                versions_list = versions
            else:
                # 版本ID存在但查询不到版本数据，可能是版本已被删除
                versions_list = []
    
    # 收集所有用户ID用于批量查询（包括负责人、报告人、验证人）
    user_ids = []
    if defect.get("assignee_id"):
        user_ids.append(defect["assignee_id"])
    if defect.get("reporter_id"):
        user_ids.append(defect["reporter_id"])
    if defect.get("verifier_id"):
        user_ids.append(defect["verifier_id"])
    
    # 批量查询用户信息
    users_dict = {}
    if user_ids:
        users = await db.users.find(
            {"_id": {"$in": user_ids}},
            {"_id": 1, "name": 1}
        ).to_list(length=None)
        users_dict = {str(user["_id"]): user for user in users}
    
    # 查询项目信息
    project_info = None
    if defect.get("project_id"):
        project = await db.projects.find_one(
            {"_id": defect["project_id"]},
            {"_id": 1, "name": 1}
        )
        if project:
            project_info = project
    
    # 获取用户信息
    assignee_id_str = str(defect["assignee_id"]) if defect.get("assignee_id") else None
    reporter_id_str = str(defect["reporter_id"]) if defect.get("reporter_id") else None
    verifier_id_str = str(defect["verifier_id"]) if defect.get("verifier_id") else None
    
    assignee_info = users_dict.get(assignee_id_str) if assignee_id_str else None
    reporter_info = users_dict.get(reporter_id_str) if reporter_id_str else None
    verifier_info = users_dict.get(verifier_id_str) if verifier_id_str else None
    
    # 使用工具函数格式化响应
    defect_data = format_defect_response(
        defect, 
        version_info=None,  # 不再使用单个版本字段 
        assignee_info=assignee_info,
        reporter_info=reporter_info,
        verifier_info=verifier_info,
        project_info=project_info,
        versions_list=versions_list,
        include_details=True
    )
    
    return success_response(data=defect_data, message="获取缺陷详情成功")

@router.post("/", response_model=ApiResponse, summary="创建缺陷")
async def create_defect(
    defect_in: DefectCreate,
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    创建缺陷
    """
    # 验证项目是否存在
    if not defect_in.project_id:
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail="project_id is required"
        )
    
    try:
        project_object_id = ObjectId(defect_in.project_id)
    except Exception:
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail="Invalid project_id format"
        )
    
    project = await db.projects.find_one({"_id": project_object_id})
    if not project:
        raise HTTPException(
            status_code=http_status.HTTP_404_NOT_FOUND,
            detail="Project not found"
        )
    
    # 检查项目成员权限
    try:
        await PermissionService.require_project_member_permission(
            defect_in.project_id,
            current_user,
            db,
            "defect:create"
        )
    except PermissionError as e:
        raise HTTPException(
            status_code=http_status.HTTP_403_FORBIDDEN,
            detail=str(e)
        )
    
    # 验证负责人是否存在
    if defect_in.assignee_id:
        try:
            assignee_object_id = ObjectId(defect_in.assignee_id)
        except Exception:
            raise HTTPException(
                status_code=http_status.HTTP_400_BAD_REQUEST,
                detail="Invalid assignee_id format"
            )
        
        assignee = await db.users.find_one({"_id": assignee_object_id})
        if not assignee:
            raise HTTPException(
                status_code=http_status.HTTP_404_NOT_FOUND,
                detail="Assignee not found"
            )
    
    # 验证验证人是否存在
    if defect_in.verifier_id:
        try:
            verifier_object_id = ObjectId(defect_in.verifier_id)
        except Exception:
            raise HTTPException(
                status_code=http_status.HTTP_400_BAD_REQUEST,
                detail="Invalid verifier_id format"
            )
        
        verifier = await db.users.find_one({"_id": verifier_object_id})
        if not verifier:
            raise HTTPException(
                status_code=http_status.HTTP_404_NOT_FOUND,
                detail="Verifier not found"
            )
    
    # 创建缺陷文档
    defect_doc = defect_in.model_dump()
    current_time = datetime.utcnow()
    defect_doc.update({
        "created_by": ObjectId(current_user.get("user_id")),
        "created_at": current_time,
        "updated_at": current_time,
        "comments": [],
        "attachments": []
    })
    
    # 转换ObjectId字段
    if defect_doc.get("project_id"):
        defect_doc["project_id"] = ObjectId(defect_doc["project_id"])
    if defect_doc.get("assignee_id"):
        defect_doc["assignee_id"] = ObjectId(defect_doc["assignee_id"])
    if defect_doc.get("reporter_id"):
        defect_doc["reporter_id"] = ObjectId(defect_doc["reporter_id"])
    if defect_doc.get("verifier_id"):
        defect_doc["verifier_id"] = ObjectId(defect_doc["verifier_id"])
    
    # 根据状态自动设置发现时间（如果状态为新建且未设置found_date）
    defect_status = defect_doc.get("status")
    if defect_status:
        # 获取"新建"状态代码
        new_status_code = await get_state_code_by_name_keywords(db, "defect", ["新建", "新", "new", "open"])
        if new_status_code and defect_status == new_status_code:
            # 如果状态为新建且未设置found_date，自动设置
            if not defect_doc.get("found_date"):
                defect_doc["found_date"] = current_time
    
    # 转换日期字段
    if defect_doc.get("found_date"):
        if isinstance(defect_doc["found_date"], str):
            try:
                defect_doc["found_date"] = datetime.fromisoformat(defect_doc["found_date"].replace('Z', '+00:00'))
            except ValueError:
                # 如果解析失败，尝试其他格式
                try:
                    defect_doc["found_date"] = datetime.fromisoformat(defect_doc["found_date"])
                except ValueError:
                    # 如果还是失败，移除该字段
                    defect_doc.pop("found_date", None)
    
    # 插入数据库
    result = await db.defects.insert_one(defect_doc)
    
    # 获取创建的缺陷
    created_defect = await db.defects.find_one({"_id": result.inserted_id})
    
    # 转换为响应模型
    defect_data = {
        "id": str(created_defect["_id"]),
        "title": created_defect["title"],
        "description": created_defect.get("description"),
        "type": created_defect["type"],
        "status": created_defect["status"],
        "priority": created_defect["priority"],
        "severity": created_defect["severity"],
        "source": created_defect["source"],
        "project_id": str(created_defect["project_id"]) if created_defect.get("project_id") else None,
        "assignee_id": str(created_defect["assignee_id"]) if created_defect.get("assignee_id") else None,
        "reporter_id": str(created_defect["reporter_id"]) if created_defect.get("reporter_id") else None,
        "verifier_id": str(created_defect["verifier_id"]) if created_defect.get("verifier_id") else None,
        "steps_to_reproduce": created_defect.get("steps_to_reproduce"),
        "expected_result": created_defect.get("expected_result"),
        "actual_result": created_defect.get("actual_result"),
        "environment_info": created_defect.get("environment_info", {}),
        "found_date": created_defect.get("found_date").isoformat() if created_defect.get("found_date") else None,
        "resolved_date": created_defect.get("resolved_date").isoformat() if created_defect.get("resolved_date") else None,
        "verified_date": created_defect.get("verified_date").isoformat() if created_defect.get("verified_date") else None,
        "completed_at": created_defect.get("completed_at").isoformat() if created_defect.get("completed_at") else None,
        "actual_completed_at": created_defect.get("actual_completed_at").isoformat() if created_defect.get("actual_completed_at") else None,
        "tags": created_defect.get("tags", []),
        "organization_id": str(created_defect["organization_id"]) if created_defect.get("organization_id") else None,
        "comments": [],
        "attachments": [],
        "created_by": str(created_defect["created_by"]),
        "created_at": created_defect["created_at"].isoformat() if created_defect.get("created_at") else None,
        "updated_at": created_defect["updated_at"].isoformat() if created_defect.get("updated_at") else None,
        "comment_count": 0,
        "attachment_count": 0
    }
    
    # 触发通知：缺陷创建时通知项目成员和负责人
    try:
        creator = await db.users.find_one({"_id": ObjectId(current_user.get("user_id"))})
        creator_name = creator.get("name") or creator.get("username", "系统") if creator else "系统"
        defect_title = created_defect.get("title", "缺陷")
        
        # 获取项目信息
        project_id = created_defect.get("project_id")
        project = None
        if project_id:
            project = await db.projects.find_one({"_id": project_id})
        
        # 收集需要通知的用户ID
        notify_user_ids = set()
        
        # 添加负责人
        assignee_id = created_defect.get("assignee_id")
        if assignee_id:
            notify_user_ids.add(assignee_id)
        
        # 添加项目成员（排除当前用户）
        if project:
            members = project.get("members", [])
            user_id = ObjectId(current_user.get("user_id"))
            for member in members:
                member_user_id = member.get("user_id")
                if member_user_id and member_user_id != user_id:
                    notify_user_ids.add(member_user_id)
        
        # 发送通知
        notification_tasks = []
        for user_id_to_notify in notify_user_ids:
            if user_id_to_notify != ObjectId(current_user.get("user_id")):
                notification_data = NotificationCreate(
                    user_id=str(user_id_to_notify),
                    type=NotificationType.DEFECT_CREATED,
                    title=f"新缺陷创建：{defect_title}",
                    content=f"{creator_name} 创建了缺陷「{defect_title}」",
                    related_type=RelatedObjectType.DEFECT,
                    related_id=str(created_defect["_id"])
                )
                notification_tasks.append(
                    NotificationService.create_notification(
                        db=db,
                        notification_data=notification_data,
                        send_email=False
                    )
                )
        
        # 异步发送所有通知
        if notification_tasks:
            asyncio.create_task(asyncio.gather(*notification_tasks, return_exceptions=True))
    except Exception as e:
        logger.warning(f"发送缺陷创建通知失败: {str(e)}")
    
    return success_response(data=defect_data, message="创建缺陷成功")

@router.put("/{defect_id}", response_model=ApiResponse, summary="更新缺陷")
async def update_defect(
    defect_id: str,
    defect_in: DefectUpdate,
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    更新缺陷信息
    """
    # 验证ObjectId格式
    try:
        object_id = ObjectId(defect_id)
    except Exception:
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail="Invalid defect ID format"
        )
    
    # 检查缺陷是否存在
    defect = await db.defects.find_one({"_id": object_id})
    if not defect:
        raise HTTPException(
            status_code=http_status.HTTP_404_NOT_FOUND,
            detail="Defect not found"
        )
    
    # 权限检查
    project_id = defect.get("project_id")
    if not project_id:
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail="Defect has no project_id"
        )
    
    project_id_str = str(project_id) if isinstance(project_id, ObjectId) else project_id
    
    # 检查项目成员权限
    try:
        await PermissionService.require_project_member_permission(
            project_id_str,
            current_user,
            db,
            "defect:update"
        )
    except PermissionError as e:
        raise HTTPException(
            status_code=http_status.HTTP_403_FORBIDDEN,
            detail=str(e)
        )
    
    # 构建更新数据
    update_data = defect_in.model_dump(exclude_unset=True)
    
    # 处理版本关联关系（如果提供了version_id）
    version_id_to_set = None
    if "version_id" in update_data:
        version_id_value = update_data.pop("version_id")  # 从update_data中移除，因为这不是缺陷表的字段
        if version_id_value:
            try:
                version_id_to_set = ObjectId(version_id_value)
                # 验证版本是否存在
                version = await db.versions.find_one({"_id": version_id_to_set})
                if not version:
                    raise HTTPException(
                        status_code=http_status.HTTP_404_NOT_FOUND,
                        detail="Version not found"
                    )
                # 验证版本是否属于同一项目
                if defect.get("project_id") and version.get("project_id") != defect.get("project_id"):
                    raise HTTPException(
                        status_code=http_status.HTTP_400_BAD_REQUEST,
                        detail="Version must belong to the same project as the defect"
                    )
            except HTTPException:
                raise
            except Exception:
                raise HTTPException(
                    status_code=http_status.HTTP_400_BAD_REQUEST,
                    detail="Invalid version_id format"
                )
        else:
            # version_id 为 null，表示要清除版本关联
            version_id_to_set = None
    
    # 如果状态字段被更新，检查是否需要自动更新时间字段
    if "status" in update_data and update_data["status"]:
        new_status = update_data["status"]
        current_time = datetime.utcnow()
        
        # 获取特定状态代码
        new_status_code = await get_state_code_by_name_keywords(db, "defect", ["新建", "新", "new", "open"])
        resolved_status_code = await get_state_code_by_name_keywords(db, "defect", ["已解决", "解决", "resolved"])
        verified_status_code = await get_state_code_by_name_keywords(db, "defect", ["已验证", "验证", "verified"])
        closed_status_code = await get_state_code_by_name_keywords(db, "defect", ["已关闭", "关闭", "closed"])
        completed_status_code = await get_state_code_by_name_keywords(db, "defect", ["已完成", "完成", "completed", "done"])
        
        # 根据状态更新相应的日期字段
        if new_status_code and new_status == new_status_code:
            # 如果状态变为新建，且发现时间未设置，自动设置
            if not defect.get("found_date"):
                update_data["found_date"] = current_time
        elif resolved_status_code and new_status == resolved_status_code:
            # 如果状态变为已解决，自动设置解决时间
            update_data["resolved_date"] = current_time
        elif verified_status_code and new_status == verified_status_code:
            # 如果状态变为已验证，自动设置验证时间
            update_data["verified_date"] = current_time
        elif (closed_status_code and new_status == closed_status_code) or (completed_status_code and new_status == completed_status_code):
            # 如果状态变为已关闭或已完成，自动设置实际完成时间
            # completed_at 不自动设置，由用户自定义，用于判断是否预期
            update_data["actual_completed_at"] = current_time
    
    # 验证优先级值（如果提供了）
    if "priority" in update_data and update_data["priority"]:
        priority_value = update_data["priority"]
        # 验证优先级是否在数据库中存在且启用
        priority_query = {"module_type": "defect", "code": priority_value, "is_enabled": True}
        priority_exists = await db.priorities.find_one(priority_query)
        if not priority_exists:
            # 如果数据库中不存在，检查是否是有效的枚举值
            try:
                # 尝试转换为枚举值进行验证
                DefectPriority(priority_value)
            except ValueError:
                raise HTTPException(
                    status_code=http_status.HTTP_400_BAD_REQUEST,
                    detail=f"Invalid priority value: {priority_value}"
                )
    
    # 转换ObjectId字段
    if "project_id" in update_data and update_data["project_id"]:
        try:
            update_data["project_id"] = ObjectId(update_data["project_id"])
        except Exception:
            raise HTTPException(
                status_code=http_status.HTTP_400_BAD_REQUEST,
                detail="Invalid project_id format"
            )
    
    if "assignee_id" in update_data and update_data["assignee_id"]:
        try:
            update_data["assignee_id"] = ObjectId(update_data["assignee_id"])
        except Exception:
            raise HTTPException(
                status_code=http_status.HTTP_400_BAD_REQUEST,
                detail="Invalid assignee_id format"
            )
    
    if "reporter_id" in update_data and update_data["reporter_id"]:
        try:
            update_data["reporter_id"] = ObjectId(update_data["reporter_id"])
        except Exception:
            raise HTTPException(
                status_code=http_status.HTTP_400_BAD_REQUEST,
                detail="Invalid reporter_id format"
            )
    
    if "verifier_id" in update_data and update_data["verifier_id"]:
        try:
            update_data["verifier_id"] = ObjectId(update_data["verifier_id"])
        except Exception:
            raise HTTPException(
                status_code=http_status.HTTP_400_BAD_REQUEST,
                detail="Invalid verifier_id format"
            )
    
    # 更新缺陷
    if update_data:
        update_data["updated_at"] = datetime.utcnow()
        await db.defects.update_one(
            {"_id": object_id},
            {"$set": update_data}
        )
    
    # 处理版本关联关系
    if version_id_to_set is not None:  # 明确提供了version_id（包括null）
        # 先删除该缺陷的所有现有版本关联
        await db.version_defect_relations.delete_many({"defect_id": object_id})
        
        # 如果提供了新的version_id，创建新的关联关系
        if version_id_to_set:
            now = datetime.utcnow()
            relation_doc = {
                "version_id": version_id_to_set,
                "defect_id": object_id,
                "requirement_id": None,
                "task_id": None,
                "parent_defect_id": None,
                "relation_type": "found_in",  # 默认关联类型
                "notes": None,
                "created_by": ObjectId(current_user.get("user_id")),
                "created_at": now,
                "updated_at": now
            }
            await db.version_defect_relations.insert_one(relation_doc)
    
    # 获取更新后的缺陷
    updated_defect = await db.defects.find_one({"_id": object_id})
    
    # 查询缺陷关联的所有版本信息（更新接口）
    defect_relations = await db.version_defect_relations.find({"defect_id": object_id}).to_list(length=None)
    versions_list = []
    if defect_relations:
        version_ids = []
        for relation in defect_relations:
            version_id = relation.get("version_id")
            if version_id:
                # 确保 version_id 是 ObjectId 类型
                if isinstance(version_id, ObjectId):
                    version_ids.append(version_id)
                elif isinstance(version_id, str) and ObjectId.is_valid(version_id):
                    version_ids.append(ObjectId(version_id))
        
        # 批量查询版本信息
        if version_ids:
            versions = await db.versions.find(
                {"_id": {"$in": version_ids}},
                {"_id": 1, "name": 1, "version_number": 1}  # 只查询这些字段
            ).to_list(length=None)
            
            # 确保查询到的版本数量与关联记录一致
            if versions and len(versions) < len(version_ids):
                # 记录警告：部分版本数据可能已被删除
                missing_count = len(version_ids) - len(versions)
                found_version_ids = {str(v["_id"]) for v in versions}
                missing_version_ids = [str(vid) for vid in version_ids if str(vid) not in found_version_ids]
                # 可以在这里添加日志记录
                # logger.warning(f"缺陷 {defect_id} 关联的 {missing_count} 个版本数据不存在: {missing_version_ids}")
            
            versions_list = versions if versions else []
    
    # 收集所有用户ID用于批量查询（包括负责人、报告人、验证人）
    user_ids = []
    if updated_defect.get("assignee_id"):
        user_ids.append(updated_defect["assignee_id"])
    if updated_defect.get("reporter_id"):
        user_ids.append(updated_defect["reporter_id"])
    if updated_defect.get("verifier_id"):
        user_ids.append(updated_defect["verifier_id"])
    
    # 批量查询用户信息
    users_dict = {}
    if user_ids:
        users = await db.users.find(
            {"_id": {"$in": user_ids}},
            {"_id": 1, "name": 1}
        ).to_list(length=None)
        users_dict = {str(user["_id"]): user for user in users}
    
    # 查询项目信息
    project_info = None
    if updated_defect.get("project_id"):
        project = await db.projects.find_one(
            {"_id": updated_defect["project_id"]},
            {"_id": 1, "name": 1}
        )
        if project:
            project_info = project
    
    # 获取用户信息
    assignee_id_str = str(updated_defect["assignee_id"]) if updated_defect.get("assignee_id") else None
    reporter_id_str = str(updated_defect["reporter_id"]) if updated_defect.get("reporter_id") else None
    verifier_id_str = str(updated_defect["verifier_id"]) if updated_defect.get("verifier_id") else None
    
    assignee_info = users_dict.get(assignee_id_str) if assignee_id_str else None
    reporter_info = users_dict.get(reporter_id_str) if reporter_id_str else None
    verifier_info = users_dict.get(verifier_id_str) if verifier_id_str else None
    
    # 触发通知：缺陷更新时通知项目成员和负责人
    if updated_defect:
        try:
            creator = await db.users.find_one({"_id": ObjectId(current_user.get("user_id"))})
            creator_name = creator.get("name") or creator.get("username", "系统") if creator else "系统"
            defect_title = updated_defect.get("title", "缺陷")
            
            # 获取项目信息
            project_id = updated_defect.get("project_id")
            project = None
            if project_id:
                project = await db.projects.find_one({"_id": project_id})
            
            # 收集需要通知的用户ID
            notify_user_ids = set()
            
            # 添加负责人
            assignee_id = updated_defect.get("assignee_id")
            if assignee_id:
                notify_user_ids.add(assignee_id)
            
            # 添加项目成员（排除当前用户）
            if project:
                members = project.get("members", [])
                user_id = ObjectId(current_user.get("user_id"))
                for member in members:
                    member_user_id = member.get("user_id")
                    if member_user_id and member_user_id != user_id:
                        notify_user_ids.add(member_user_id)
            
            # 发送通知
            notification_tasks = []
            for user_id_to_notify in notify_user_ids:
                if user_id_to_notify != ObjectId(current_user.get("user_id")):
                    notification_data = NotificationCreate(
                        user_id=str(user_id_to_notify),
                        type=NotificationType.DEFECT_UPDATED,
                        title=f"缺陷更新：{defect_title}",
                        content=f"{creator_name} 更新了缺陷「{defect_title}」",
                        related_type=RelatedObjectType.DEFECT,
                        related_id=defect_id
                    )
                    notification_tasks.append(
                        NotificationService.create_notification(
                            db=db,
                            notification_data=notification_data,
                            send_email=False
                        )
                    )
            
            # 异步发送所有通知
            if notification_tasks:
                asyncio.create_task(asyncio.gather(*notification_tasks, return_exceptions=True))
        except Exception as e:
            logger.warning(f"发送缺陷更新通知失败: {str(e)}")
    
    # 使用工具函数格式化响应
    return success_response(
        data=format_defect_response(
            updated_defect, 
            version_info=None,  # 不再使用单个版本字段 
            assignee_info=assignee_info,
            reporter_info=reporter_info,
            verifier_info=verifier_info,
            project_info=project_info,
            versions_list=versions_list,
            include_details=True
        ), 
        message="更新缺陷成功"
    )

@router.delete("/{defect_id}", response_model=ApiResponse, summary="删除缺陷")
async def delete_defect(
    defect_id: str,
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    删除缺陷
    """
    # 验证ObjectId格式
    try:
        object_id = ObjectId(defect_id)
    except Exception:
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail="Invalid defect ID format"
        )
    
    # 检查缺陷是否存在
    defect = await db.defects.find_one({"_id": object_id})
    if not defect:
        raise HTTPException(
            status_code=http_status.HTTP_404_NOT_FOUND,
            detail="Defect not found"
        )
    
    # 权限检查
    project_id = defect.get("project_id")
    if not project_id:
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail="Defect has no project_id"
        )
    
    project_id_str = str(project_id) if isinstance(project_id, ObjectId) else project_id
    
    # 检查项目成员权限
    try:
        await PermissionService.require_project_member_permission(
            project_id_str,
            current_user,
            db,
            "defect:delete"
        )
    except PermissionError as e:
        raise HTTPException(
            status_code=http_status.HTTP_403_FORBIDDEN,
            detail=str(e)
        )
    
    # 检查是否允许删除（保留原有逻辑，允许创建者删除）
    user_id = ObjectId(current_user.get("user_id"))
    is_creator = defect.get("created_by") == user_id
    if not is_creator:
        raise HTTPException(
            status_code=http_status.HTTP_403_FORBIDDEN,
            detail="Not enough permissions to delete this defect"
        )
    
    # 触发通知：缺陷删除时通知项目成员和负责人
    try:
        creator = await db.users.find_one({"_id": ObjectId(current_user.get("user_id"))})
        creator_name = creator.get("name") or creator.get("username", "系统") if creator else "系统"
        defect_title = defect.get("title", "缺陷")
        
        # 获取项目信息
        project_id = defect.get("project_id")
        project = None
        if project_id:
            project = await db.projects.find_one({"_id": project_id})
        
        # 收集需要通知的用户ID
        notify_user_ids = set()
        
        # 添加负责人
        assignee_id = defect.get("assignee_id")
        if assignee_id:
            notify_user_ids.add(assignee_id)
        
        # 添加项目成员（排除当前用户）
        if project:
            members = project.get("members", [])
            user_id = ObjectId(current_user.get("user_id"))
            for member in members:
                member_user_id = member.get("user_id")
                if member_user_id and member_user_id != user_id:
                    notify_user_ids.add(member_user_id)
        
        # 发送通知
        notification_tasks = []
        for user_id_to_notify in notify_user_ids:
            if user_id_to_notify != ObjectId(current_user.get("user_id")):
                notification_data = NotificationCreate(
                    user_id=str(user_id_to_notify),
                    type=NotificationType.DEFECT_DELETED,
                    title=f"缺陷已删除：{defect_title}",
                    content=f"{creator_name} 删除了缺陷「{defect_title}」",
                    related_type=RelatedObjectType.DEFECT,
                    related_id=defect_id
                )
                notification_tasks.append(
                    NotificationService.create_notification(
                        db=db,
                        notification_data=notification_data,
                        send_email=False
                    )
                )
        
        # 异步发送所有通知
        if notification_tasks:
            asyncio.create_task(asyncio.gather(*notification_tasks, return_exceptions=True))
    except Exception as e:
        logger.warning(f"发送缺陷删除通知失败: {str(e)}")
    
    # 删除缺陷
    await db.defects.delete_one({"_id": object_id})
    
    return success_response(message="Defect deleted successfully")

@router.patch("/{defect_id}/status", response_model=ApiResponse, summary="更新缺陷状态")
async def update_defect_status(
    defect_id: str,
    status_update: DefectStatusUpdate,
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    更新缺陷状态
    """
    # 验证ObjectId格式
    try:
        object_id = ObjectId(defect_id)
    except Exception:
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail="Invalid defect ID format"
        )
    
    # 检查缺陷是否存在
    defect = await db.defects.find_one({"_id": object_id})
    if not defect:
        raise HTTPException(
            status_code=http_status.HTTP_404_NOT_FOUND,
            detail="Defect not found"
        )
    
    # 检查项目成员权限
    project_id = defect.get("project_id")
    if project_id:
        from app.services.permission_service import PermissionService
        try:
            project_id_str = str(project_id) if isinstance(project_id, ObjectId) else project_id
            await PermissionService.require_project_member_permission(
                project_id_str,
                current_user,
                db,
                "defect:update"
            )
        except Exception as e:
            raise HTTPException(
                status_code=http_status.HTTP_403_FORBIDDEN,
                detail=str(e) or "Not enough permissions to update defect status"
            )
    else:
        # 如果没有项目ID，使用旧的权限检查（向后兼容）
        user_permissions = current_user.get("permissions", [])
        user_id = ObjectId(current_user.get("user_id"))
        
        has_permission = (
            Permissions.DEFECT_UPDATE in user_permissions or
            Permissions.ADMIN in user_permissions or
            defect.get("assignee_id") == user_id or
            defect.get("reporter_id") == user_id or
            defect.get("verifier_id") == user_id or
            defect.get("created_by") == user_id
        )
        
        if not has_permission:
            raise HTTPException(
                status_code=http_status.HTTP_403_FORBIDDEN,
                detail="Not enough permissions to update defect status"
            )
    
    # 构建更新数据
    update_data = {
        "status": status_update.status,
        "updated_at": datetime.utcnow()
    }
    
    # 验证状态代码
    is_valid = await validate_state_code(db, "defect", status_update.status)
    if not is_valid:
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail=f"Invalid status value: {status_update.status}"
        )
    
    # 获取特定状态代码
    new_status_code = await get_state_code_by_name_keywords(db, "defect", ["新建", "新", "new", "open"])
    resolved_status_code = await get_state_code_by_name_keywords(db, "defect", ["已解决", "解决", "resolved"])
    verified_status_code = await get_state_code_by_name_keywords(db, "defect", ["已验证", "验证", "verified"])
    closed_status_code = await get_state_code_by_name_keywords(db, "defect", ["已关闭", "关闭", "closed"])
    completed_status_code = await get_state_code_by_name_keywords(db, "defect", ["已完成", "完成", "completed", "done"])
    
    current_time = datetime.utcnow()
    
    # 根据状态更新相应的日期字段
    if new_status_code and status_update.status == new_status_code:
        # 如果状态变为新建，且发现时间未设置，自动设置
        if not defect.get("found_date"):
            update_data["found_date"] = current_time
    elif resolved_status_code and status_update.status == resolved_status_code:
        # 如果状态变为已解决，自动设置解决时间
        update_data["resolved_date"] = current_time
    elif verified_status_code and status_update.status == verified_status_code:
        # 如果状态变为已验证，自动设置验证时间
        update_data["verified_date"] = current_time
    elif (closed_status_code and status_update.status == closed_status_code) or (completed_status_code and status_update.status == completed_status_code):
        # 如果状态变为已关闭或已完成，自动设置实际完成时间
        # completed_at 不自动设置，由用户自定义，用于判断是否预期
        update_data["actual_completed_at"] = current_time
    
    # 更新缺陷状态
    await db.defects.update_one(
        {"_id": object_id},
        {"$set": update_data}
    )
    
    # 触发版本状态流转检查（异步，不阻塞响应）
    try:
        from app.utils.version_transition_trigger import trigger_version_transition_check
        import asyncio
        asyncio.create_task(trigger_version_transition_check(db, "defect", object_id))
    except Exception as e:
        import logging
        logger = logging.getLogger(__name__)
        logger.warning(f"触发版本状态流转检查失败: {str(e)}")
    
    return success_response(message="Defect status updated successfully")

@router.patch("/{defect_id}/assignee", response_model=ApiResponse, summary="更新缺陷负责人")
async def update_defect_assignee(
    defect_id: str,
    assignee_update: DefectAssigneeUpdate,
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    更新缺陷负责人
    """
    # 验证ObjectId格式
    try:
        object_id = ObjectId(defect_id)
    except Exception:
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail="Invalid defect ID format"
        )
    
    # 检查缺陷是否存在
    defect = await db.defects.find_one({"_id": object_id})
    if not defect:
        raise HTTPException(
            status_code=http_status.HTTP_404_NOT_FOUND,
            detail="Defect not found"
        )
    
    # 检查项目成员权限
    project_id = defect.get("project_id")
    if project_id:
        from app.services.permission_service import PermissionService
        try:
            project_id_str = str(project_id) if isinstance(project_id, ObjectId) else project_id
            await PermissionService.require_project_member_permission(
                project_id_str,
                current_user,
                db,
                "defect:assign"
            )
        except Exception as e:
            raise HTTPException(
                status_code=http_status.HTTP_403_FORBIDDEN,
                detail=str(e) or "Not enough permissions to assign defect"
            )
    else:
        # 如果没有项目ID，使用旧的权限检查（向后兼容）
        user_permissions = current_user.get("permissions", [])
        user_id = ObjectId(current_user.get("user_id"))
        
        has_permission = (
            Permissions.DEFECT_ASSIGN in user_permissions or
            Permissions.ADMIN in user_permissions or
            defect.get("created_by") == user_id
        )
        
        if not has_permission:
            raise HTTPException(
                status_code=http_status.HTTP_403_FORBIDDEN,
                detail="Not enough permissions to assign defect"
            )
    
    # 验证新负责人是否存在
    if assignee_update.assignee_id:
        try:
            assignee_object_id = ObjectId(assignee_update.assignee_id)
        except Exception:
            raise HTTPException(
                status_code=http_status.HTTP_400_BAD_REQUEST,
                detail="Invalid assignee_id format"
            )
        
        assignee = await db.users.find_one({"_id": assignee_object_id})
        if not assignee:
            raise HTTPException(
                status_code=http_status.HTTP_404_NOT_FOUND,
                detail="Assignee not found"
            )
    else:
        assignee_object_id = None
    
    # 更新缺陷负责人
    await db.defects.update_one(
        {"_id": object_id},
        {
            "$set": {
                "assignee_id": assignee_object_id,
                "updated_at": datetime.utcnow()
            }
        }
    )
    
    # 获取更新后的缺陷信息
    updated_defect = await db.defects.find_one({"_id": object_id})
    
    # 获取负责人信息
    assignee_info = None
    if updated_defect and updated_defect.get("assignee_id"):
        assignee = await db.users.find_one(
            {"_id": updated_defect["assignee_id"]},
            {"_id": 1, "name": 1}
        )
        if assignee:
            assignee_info = assignee
    
    # 构建响应数据
    response_data = {
        "id": str(updated_defect["_id"]),
        "assignee_id": str(updated_defect["assignee_id"]) if updated_defect.get("assignee_id") else None,
        "assignee_name": assignee_info.get("name") if assignee_info else None
    }
    
    return success_response(data=response_data, message="Defect assignee updated successfully")


@router.get("/{defect_id}/attachments", response_model=ApiResponse, summary="获取缺陷附件列表")
async def get_defect_attachments(
    defect_id: str,
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    获取缺陷附件列表
    """
    # 验证ObjectId格式
    try:
        object_id = ObjectId(defect_id)
    except Exception:
        raise HTTPException(
            status_code=http_status.HTTP_400_BAD_REQUEST,
            detail="Invalid defect ID format"
        )
    
    # 查询缺陷
    defect = await db.defects.find_one({"_id": object_id})
    if not defect:
        raise HTTPException(
            status_code=http_status.HTTP_404_NOT_FOUND,
            detail="Defect not found"
        )
    
    # 检查项目成员权限
    project_id = defect.get("project_id")
    if project_id:
        from app.services.permission_service import PermissionService
        try:
            project_id_str = str(project_id) if isinstance(project_id, ObjectId) else project_id
            has_access = await PermissionService.check_project_member_permission(
                project_id_str,
                current_user,
                db,
                "defect:read"
            )
            if not has_access:
                raise HTTPException(
                    status_code=http_status.HTTP_403_FORBIDDEN,
                    detail="Not enough permissions to access this defect"
                )
        except HTTPException:
            raise
        except Exception:
            raise HTTPException(
                status_code=http_status.HTTP_403_FORBIDDEN,
                detail="Not enough permissions to access this defect"
            )
    else:
        # 如果没有项目ID，使用旧的权限检查（向后兼容）
        user_permissions = current_user.get("permissions", [])
        user_id = ObjectId(current_user.get("user_id"))
        
        has_access = (
            Permissions.DEFECT_READ_ALL in user_permissions or
            Permissions.ADMIN in user_permissions or
            defect.get("assignee_id") == user_id or
            defect.get("reporter_id") == user_id or
            defect.get("verifier_id") == user_id
        )
        
        if not has_access:
            raise HTTPException(
                status_code=http_status.HTTP_403_FORBIDDEN,
                detail="Not enough permissions to access this defect"
            )
    
    # 获取附件列表
    attachments = defect.get("attachments", [])
    
    # 格式化附件信息
    formatted_attachments = []
    for attachment in attachments:
        uploaded_at = attachment.get("uploaded_at")
        if isinstance(uploaded_at, datetime):
            uploaded_at_str = uploaded_at.isoformat()
        elif uploaded_at:
            uploaded_at_str = str(uploaded_at)
        else:
            uploaded_at_str = None
        
        formatted_attachments.append({
            "id": str(attachment.get("id")),
            "filename": attachment.get("filename", ""),
            "file_path": attachment.get("file_path", ""),
            "file_size": attachment.get("file_size", 0),
            "content_type": attachment.get("content_type", ""),
            "uploaded_by": str(attachment.get("uploaded_by")) if attachment.get("uploaded_by") else None,
            "uploaded_at": uploaded_at_str
        })
    
    return success_response(
        data=formatted_attachments,
        message="获取附件列表成功"
    )

@router.patch("/batch", response_model=ApiResponse, summary="批量更新缺陷")
async def batch_update_defects(
    body: dict = Body(...),
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    批量更新缺陷
    """
    defect_ids = body.get("defect_ids", [])
    
    if isinstance(defect_ids, str):
        defect_ids = [defect_ids.strip()]
    elif isinstance(defect_ids, (set, tuple)):
        defect_ids = [str(i).strip() for i in defect_ids]
    elif not isinstance(defect_ids, list):
        return error_response(
            error_type=ErrorTypes.INVALID_PARAMETER,
            message="Invalid defect_ids format, expected array of defect IDs",
            code=400
        )
    else:
        defect_ids = [str(i).strip() for i in defect_ids if i is not None]
    
    updates = {k: v for k, v in body.items() if k != "defect_ids" and v is not None}
    
    if not defect_ids:
        return error_response(
            error_type=ErrorTypes.INVALID_PARAMETER,
            message="Defect IDs are required",
            code=400
        )
    
    if not updates:
        return error_response(
            error_type=ErrorTypes.INVALID_PARAMETER,
            message="No update fields provided",
            code=400
        )
    
    # 权限检查
    user_permissions = current_user.get("permissions", [])
    user_id = ObjectId(current_user.get("user_id"))
    
    # 判断是否为分配操作（只更新 assignee_id）
    is_assign_operation = "assignee_id" in updates and len(updates) == 1
    
    # 根据操作类型检查基础权限
    if is_assign_operation:
        # 分配操作需要 DEFECT_ASSIGN 权限（如果存在）或 DEFECT_UPDATE 权限
        if (Permissions.DEFECT_UPDATE not in user_permissions and 
            Permissions.ADMIN not in user_permissions):
            return error_response(
                error_type=ErrorTypes.PERMISSION_DENIED_ERROR,
                message="Not enough permissions to assign defects",
                code=403
            )
    else:
        # 其他更新操作需要 DEFECT_UPDATE 权限
        if Permissions.DEFECT_UPDATE not in user_permissions and Permissions.ADMIN not in user_permissions:
            return error_response(
                error_type=ErrorTypes.PERMISSION_DENIED_ERROR,
                message="Not enough permissions to update defects",
                code=403
            )
    
    # 验证ObjectId格式
    object_ids: List[ObjectId] = []
    invalid_ids: List[str] = []
    for defect_id in defect_ids:
        if not defect_id:
            continue
        if ObjectId.is_valid(defect_id):
            object_ids.append(ObjectId(defect_id))
        else:
            invalid_ids.append(defect_id)
    
    if invalid_ids:
        return error_response(
            error_type=ErrorTypes.INVALID_PARAMETER,
            message="Invalid defect ID format",
            details={"invalid_ids": invalid_ids},
            code=400
        )
    
    # 查询缺陷
    defects = await db.defects.find({"_id": {"$in": object_ids}}).to_list(length=None)
    
    if len(defects) != len(object_ids):
        return error_response(
            error_type=ErrorTypes.NOT_FOUND,
            message="Some defects not found",
            code=404
        )
    
    # 对于非管理员，检查每个缺陷的权限
    if Permissions.ADMIN not in user_permissions:
        updatable_ids = []
        for defect in defects:
            if is_assign_operation:
                # 分配操作：检查分配权限
                can_assign = (
                    Permissions.DEFECT_UPDATE in user_permissions or
                    defect.get("created_by") == user_id
                )
                
                # 检查项目权限
                if not can_assign and defect.get("project_id"):
                    project = await db.projects.find_one({"_id": defect["project_id"]})
                    if project:
                        can_assign = (
                            project["created_by"] == user_id or
                            any(
                                member["user_id"] == user_id and "defect:assign" in member.get("permissions", [])
                                for member in project.get("members", [])
                            )
                        )
                
                if can_assign:
                    updatable_ids.append(defect["_id"])
            else:
                # 其他更新操作：检查更新权限
                can_update = (
                    Permissions.DEFECT_UPDATE in user_permissions or
                    defect.get("assignee_id") == user_id or
                    defect.get("reporter_id") == user_id or
                    defect.get("verifier_id") == user_id or
                    defect.get("created_by") == user_id
                )
                
                # 检查项目权限
                if not can_update and defect.get("project_id"):
                    project = await db.projects.find_one({"_id": defect["project_id"]})
                    if project:
                        can_update = (
                            project["created_by"] == user_id or
                            any(
                                member["user_id"] == user_id and "defect:update" in member.get("permissions", [])
                                for member in project.get("members", [])
                            )
                        )
                
                if can_update:
                    updatable_ids.append(defect["_id"])
        
        if not updatable_ids:
            operation_type = "分配" if is_assign_operation else "更新"
            return error_response(
                error_type=ErrorTypes.PERMISSION_DENIED_ERROR,
                message=f"Not enough permissions to {operation_type} any of the specified defects",
                code=403
            )
        
        object_ids = updatable_ids
    
    # 构建更新数据
    update_data = {"updated_at": datetime.utcnow()}
    
    # 处理状态更新
    if "status" in updates:
        is_valid = await validate_state_code(db, "defect", updates["status"])
        if not is_valid:
            return error_response(
                error_type=ErrorTypes.INVALID_PARAMETER,
                message=f"Invalid status value: {updates['status']}",
                code=400
            )
        new_status = updates["status"]
        update_data["status"] = new_status
        
        # 根据状态自动更新时间字段
        current_time = datetime.utcnow()
        new_status_code = await get_state_code_by_name_keywords(db, "defect", ["新建", "新", "new", "open"])
        resolved_status_code = await get_state_code_by_name_keywords(db, "defect", ["已解决", "解决", "resolved"])
        verified_status_code = await get_state_code_by_name_keywords(db, "defect", ["已验证", "验证", "verified"])
        closed_status_code = await get_state_code_by_name_keywords(db, "defect", ["已关闭", "关闭", "closed"])
        completed_status_code = await get_state_code_by_name_keywords(db, "defect", ["已完成", "完成", "completed", "done"])
        
        # 根据状态更新相应的日期字段
        if new_status_code and new_status == new_status_code:
            # 如果状态变为新建，批量更新时需要在每个缺陷上检查是否已设置found_date
            # 这里不设置found_date，在批量更新时单独处理
            pass  # 批量更新时，如果found_date未设置，会在更新时自动设置
        elif resolved_status_code and new_status == resolved_status_code:
            # 如果状态变为已解决，自动设置解决时间
            update_data["resolved_date"] = current_time
        elif verified_status_code and new_status == verified_status_code:
            # 如果状态变为已验证，自动设置验证时间
            update_data["verified_date"] = current_time
        elif (closed_status_code and new_status == closed_status_code) or (completed_status_code and new_status == completed_status_code):
            # 如果状态变为已关闭或已完成，自动设置实际完成时间
            # completed_at 不自动设置，由用户自定义，用于判断是否预期
            update_data["actual_completed_at"] = current_time
    
    # 处理负责人更新
    if "assignee_id" in updates:
        try:
            assignee_id = ObjectId(updates["assignee_id"]) if updates["assignee_id"] else None
            if assignee_id:
                assignee = await db.users.find_one({"_id": assignee_id})
                if not assignee:
                    return error_response(
                        error_type=ErrorTypes.NOT_FOUND,
                        message="Assignee not found",
                        code=404
                    )
            update_data["assignee_id"] = assignee_id
        except Exception:
            return error_response(
                error_type=ErrorTypes.INVALID_PARAMETER,
                message="Invalid assignee_id format",
                code=400
            )
    
    # 处理报告人更新
    if "reporter_id" in updates:
        try:
            reporter_id = ObjectId(updates["reporter_id"]) if updates["reporter_id"] else None
            if reporter_id:
                reporter = await db.users.find_one({"_id": reporter_id})
                if not reporter:
                    return error_response(
                        error_type=ErrorTypes.NOT_FOUND,
                        message="Reporter not found",
                        code=404
                    )
            update_data["reporter_id"] = reporter_id
        except Exception:
            return error_response(
                error_type=ErrorTypes.INVALID_PARAMETER,
                message="Invalid reporter_id format",
                code=400
            )
    
    # 处理优先级更新
    if "priority" in updates:
        priority_value = updates["priority"]
        # 验证优先级是否在数据库中存在且启用
        priority_query = {"module_type": "defect", "code": priority_value, "is_enabled": True}
        priority_exists = await db.priorities.find_one(priority_query)
        if not priority_exists:
            # 如果数据库中不存在，检查是否是有效的枚举值
            try:
                # 尝试转换为枚举值进行验证
                DefectPriority(priority_value)
            except ValueError:
                return error_response(
                    error_type=ErrorTypes.INVALID_PARAMETER,
                    message=f"Invalid priority value: {priority_value}",
                    code=400
                )
        update_data["priority"] = priority_value
    
    # 处理严重程度更新
    if "severity" in updates:
        update_data["severity"] = updates["severity"]
    
    # 处理缺陷类型更新
    if "type" in updates:
        update_data["type"] = updates["type"]
    
    # 处理缺陷来源更新
    if "source" in updates:
        update_data["source"] = updates["source"]
    
    # 处理项目更新
    if "project_id" in updates:
        try:
            project_id = ObjectId(updates["project_id"]) if updates["project_id"] else None
            if project_id:
                project = await db.projects.find_one({"_id": project_id})
                if not project:
                    return error_response(
                        error_type=ErrorTypes.NOT_FOUND,
                        message="Project not found",
                        code=404
                    )
            update_data["project_id"] = project_id
        except Exception:
            return error_response(
                error_type=ErrorTypes.INVALID_PARAMETER,
                message="Invalid project_id format",
                code=400
            )
    
    # 批量更新缺陷
    # 如果状态更新为"新建"且需要设置found_date，需要特殊处理
    if "status" in updates:
        # 获取新建状态代码（如果之前没有获取）
        if "new_status_code" not in locals():
            new_status_code = await get_state_code_by_name_keywords(db, "defect", ["新建", "新", "new", "open"])
        if new_status_code and update_data.get("status") == new_status_code:
            # 对于新建状态，需要为found_date未设置的缺陷设置found_date
            # 使用两次更新：先更新found_date未设置的，再更新其他字段
            current_time = datetime.utcnow()
            await db.defects.update_many(
                {"_id": {"$in": object_ids}, "found_date": {"$exists": False}},
                {"$set": {"found_date": current_time}}
            )
            # 移除found_date，因为已经单独处理了
            update_data_without_found = {k: v for k, v in update_data.items() if k != "found_date"}
            result = await db.defects.update_many(
                {"_id": {"$in": object_ids}},
                {"$set": update_data_without_found}
            )
        else:
            result = await db.defects.update_many(
                {"_id": {"$in": object_ids}},
                {"$set": update_data}
            )
    else:
        result = await db.defects.update_many(
            {"_id": {"$in": object_ids}},
            {"$set": update_data}
        )
    
    return success_response(
        data={"updated_count": result.modified_count, "failed_count": len(object_ids) - result.modified_count},
        message=f"成功更新 {result.modified_count} 个缺陷"
    )

@router.delete("/batch", response_model=ApiResponse, summary="批量删除缺陷")
async def batch_delete_defects(
    body: dict = Body(...),
    current_user: dict = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_db)
) -> Any:
    """
    批量删除缺陷
    """
    defect_ids = body.get("defect_ids", [])
    
    if isinstance(defect_ids, str):
        defect_ids = [defect_ids.strip()]
    elif isinstance(defect_ids, (set, tuple)):
        defect_ids = [str(i).strip() for i in defect_ids]
    elif not isinstance(defect_ids, list):
        return error_response(
            error_type=ErrorTypes.INVALID_PARAMETER,
            message="Invalid defect_ids format, expected array of defect IDs",
            code=400
        )
    else:
        defect_ids = [str(i).strip() for i in defect_ids if i is not None]
    
    if not defect_ids:
        return error_response(
            error_type=ErrorTypes.INVALID_PARAMETER,
            message="Defect IDs are required",
            code=400
        )
    
    # 权限检查
    user_permissions = current_user.get("permissions", [])
    user_id = ObjectId(current_user.get("user_id"))
    
    if Permissions.DEFECT_DELETE_ALL not in user_permissions and Permissions.ADMIN not in user_permissions:
        return error_response(
            error_type=ErrorTypes.PERMISSION_DENIED_ERROR,
            message="Not enough permissions to delete defects",
            code=403
        )
    
    # 验证ObjectId格式
    object_ids: List[ObjectId] = []
    invalid_ids: List[str] = []
    for defect_id in defect_ids:
        if not defect_id:
            continue
        try:
            object_ids.append(ObjectId(defect_id))
        except Exception:
            invalid_ids.append(defect_id)
    
    if invalid_ids:
        return error_response(
            error_type=ErrorTypes.INVALID_PARAMETER,
            message=f"Invalid defect IDs: {', '.join(invalid_ids)}",
            code=400
        )
    
    if not object_ids:
        return error_response(
            error_type=ErrorTypes.INVALID_PARAMETER,
            message="No valid defect IDs provided",
            code=400
        )
    
    # 查询缺陷并检查权限
    defects = await db.defects.find({"_id": {"$in": object_ids}}).to_list(length=None)
    
    if not defects:
        return error_response(
            error_type=ErrorTypes.NOT_FOUND,
            message="No defects found",
            code=404
        )
    
    # 对于非管理员，检查每个缺陷的权限
    if Permissions.ADMIN not in user_permissions:
        deletable_ids = []
        for defect in defects:
            # 检查是否有权限删除此缺陷
            can_delete = (
                defect.get("created_by") == user_id
            )
            
            # 检查项目权限
            if not can_delete and defect.get("project_id"):
                project = await db.projects.find_one({"_id": defect["project_id"]})
                if project:
                    can_delete = (
                        project.get("created_by") == user_id or
                        any(
                            member["user_id"] == user_id and "defect:delete" in member.get("permissions", [])
                            for member in project.get("members", [])
                        )
                    )
            
            if can_delete:
                deletable_ids.append(defect["_id"])
        
        if not deletable_ids:
            return error_response(
                error_type=ErrorTypes.PERMISSION_DENIED_ERROR,
                message="Not enough permissions to delete any of the specified defects",
                code=403
            )
        
        object_ids = deletable_ids
    
    # 批量删除缺陷
    result = await db.defects.delete_many({"_id": {"$in": object_ids}})
    
    return success_response(
        data={"deleted_count": result.deleted_count},
        message=f"成功删除 {result.deleted_count} 个缺陷"
    )
